/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package org.xptracker.services.reports;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.map.LazySortedMap;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeTableXYDataset;
import org.joda.time.DateTime;
import org.xptracker.domain.Domain;
import org.xptracker.domain.Story;
import org.xptracker.domain.TimeSegment;

import java.awt.*;
import java.text.NumberFormat;
import java.util.*;

public class ChartBuildingReportRecipient implements TimeSegmentReportRecipient {
  protected final TimeSegment segment;
  private final Map<Story, Double> initialEstimates = new HashMap<Story, Double>();
  private final Map<Story, Map<Date, Double>> periodEstimates = new HashMap<Story, Map<Date, Double>>();
  private final Set<Date> encounteredDates = new TreeSet<Date>();

  @SuppressWarnings("unchecked")
  private final Map<Date, Double> additions = LazySortedMap.decorate(new TreeMap(), new Factory() {
    public Object create() {
      return (double) 0;
    }
  });

  @SuppressWarnings("unchecked")
  private final Map<Date, Double> removals = LazySortedMap.decorate(new TreeMap(), new Factory() {
    public Object create() {
      return (double) 0;
    }
  });
  private JFreeChart chart;

  public ChartBuildingReportRecipient(TimeSegment segment) {
    this.segment = segment;
  }

  protected void addEstimate(Date date, Story s, double amount) {
    chart = null;
    if (!initialEstimates.containsKey(s)) {
      initialEstimates.put(s, amount);
    }
    getPeriodEstimates(s).put(date, amount);
    encounteredDates.add(date);
  }

  protected void removeEstimate(Date date, Story s, double amount) {
    chart = null;
    getPeriodEstimates(s).remove(date);
    encounteredDates.add(date);
  }

  private void accumulate(Date date, Map<Date, Double> map, double amount) {
    if (!segmentStartedOn(date)) {
      map.put(date, map.get(date) + amount);
    }
  }

  public JFreeChart buildChart() {
    if (chart == null) {
      TimeSeriesCollection dataset = new TimeSeriesCollection();
      TimeTableXYDataset addDataset = new TimeTableXYDataset();
      TimeTableXYDataset removeDataset = new TimeTableXYDataset();
      TimeSeries currentSeries = new TimeSeries("Current");
      TimeSeries initialSeries = new TimeSeries("Initial");

      double addAccumulator = 0.0;
      double removeAccumulator = 0.0;

      for (Date d : encounteredDates) {
        if (d.after(new Date())) {
          break;
        }
        double currentAccumulator = 0.0;
        double initialAccumulator = 0.0;

        for (Story s : periodEstimates.keySet()) {
          Double curValue = periodEstimates.get(s).get(d);
          if (curValue != null) {
            initialAccumulator += initialEstimates.get(s);
            currentAccumulator += curValue;
          }
        }
        initialSeries.addOrUpdate(new Day(d), initialAccumulator);
        currentSeries.addOrUpdate(new Day(d), currentAccumulator);

        if (additions.containsKey(d)) {
          Double add = additions.get(d);
          addDataset.add(new Day(d), addAccumulator, "Adds");
          addDataset.add(new Day(d), add, "Adds (New)");
          addAccumulator += add;
        }

        if (removals.containsKey(d)) {
          Double removal = removals.get(d);
          removeDataset.add(new Day(d), removeAccumulator, "Removes");
          removeDataset.add(new Day(d), removal * -1, "Removes (New)");
          removeAccumulator -= removal;
        }
      }
      dataset.addSeries(initialSeries);
      dataset.addSeries(currentSeries);

      chart = ChartFactory.createTimeSeriesChart(null, "Time", "Amount", dataset, false, false, false);
      chart.getXYPlot().setDataset(1, addDataset);
      chart.getXYPlot().setDataset(2, removeDataset);

      XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer();
      lineRenderer.setSeriesPaint(0, Color.GRAY);
      lineRenderer.setSeriesPaint(1, Color.BLACK);
      lineRenderer.setSeriesShapesVisible(0, false);
      lineRenderer.setSeriesShapesVisible(1, false);
      lineRenderer.setSeriesStroke(0, new BasicStroke(2.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
          new float[]{10, 10, 3, 10}, 0.0f));
      lineRenderer.setSeriesStroke(1, new BasicStroke(2.0f));

      StackedXYBarRenderer addBarRenderer = new StackedXYBarRenderer();
      addBarRenderer.setSeriesPaint(0, Color.decode("#FFAAAA"));
      addBarRenderer.setSeriesPaint(1, Color.RED);

      StackedXYBarRenderer removeBarRenderer = new StackedXYBarRenderer();
      removeBarRenderer.setSeriesPaint(0, Color.decode("#AAAAFF"));
      removeBarRenderer.setSeriesPaint(1, Color.BLUE);
      chart.getXYPlot().setRenderers(new XYItemRenderer[]{lineRenderer, addBarRenderer, removeBarRenderer});
      NumberAxis axis = (NumberAxis) chart.getXYPlot().getRangeAxis();
      axis.setNumberFormatOverride(NumberFormat.getIntegerInstance());
      ((DateAxis) chart.getXYPlot().getDomainAxis()).setRange(segment.getStartDate(), segment.getStopDate());

      chart.setBackgroundPaint(Color.WHITE);
    }
    return chart;
  }

  private Map<Date, Double> getPeriodEstimates(Story s) {
    Map<Date, Double> estimates = periodEstimates.get(s);
    if (estimates == null) {
      estimates = new HashMap<Date, Double>();
      periodEstimates.put(s, estimates);
    }
    return estimates;
  }

  public void storyAdded(Domain domain, Date date, Story s, double amount) {
    accumulate(date, additions, amount);
    addEstimate(date, s, amount);
  }

  public void storyRemoved(Domain domain, Date date, Story s, double amount) {
    accumulate(date, removals, amount);
    removeEstimate(date, s, amount);
  }

  public void storyCompleted(Domain domain, Date date, Story s, double amount) {
    removeEstimate(date, s, amount);
  }

  public void storyRemains(Domain domain, Date date, Story s, double amount) {
    addEstimate(date, s, amount);
  }

  protected boolean segmentStartedOn(Date date) {
    return new DateTime(date).toDateMidnight().equals(new DateTime(segment.getStartDate()).toDateMidnight());
  }
}
